/**
 * 初始化 WebGL 上下文和着色器程序。
 * @param {HTMLCanvasElement} canvas - WebGL 上下文所在的 canvas 元素。
 */
export const initializeWebGL = canvas => {
  const gl =
    canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  if (!gl) {
    console.error("WebGL not supported!");
    return;
  }
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  const vertexShaderSource = `
    attribute lowp vec4 a_vertexPosition;
    attribute vec2 a_texturePosition;
    varying vec2 v_texCoord;
    void main() {
        gl_Position = a_vertexPosition;
        v_texCoord = a_texturePosition;
    }
  `;
  const fragmentShaderSource = `
    precision lowp float;
    uniform sampler2D samplerY;
    uniform sampler2D samplerU;
    uniform sampler2D samplerV;
    varying vec2 v_texCoord;
    void main() {
        float r, g, b, y, u, v, fYmul;
        y = texture2D(samplerY, v_texCoord).r;
        u = texture2D(samplerU, v_texCoord).r;
        v = texture2D(samplerV, v_texCoord).r;
        fYmul = y * 1.1643828125;
        r = fYmul + 1.59602734375 * v - 0.870787598;
        g = fYmul - 0.39176171875 * u - 0.81296875 * v + 0.52959375;
        b = fYmul + 2.01723046875 * u - 1.081389160375;
        gl_FragColor = vec4(r, g, b, 1.0);
    }
  `;
  const vertexShader: WebGLShader | null = compileShader(
    gl,
    vertexShaderSource,
    gl.VERTEX_SHADER
  );
  const fragmentShader: WebGLShader | null = compileShader(
    gl,
    fragmentShaderSource,
    gl.FRAGMENT_SHADER
  );
  const program: WebGLProgram | null = createProgram(
    gl,
    vertexShader,
    fragmentShader
  );
  initVertexBuffers(gl, program);
  gl.activeTexture(gl.TEXTURE0);
  gl.y = createTexture(gl);
  gl.uniform1i(gl.getUniformLocation(program, "samplerY"), 0);
  gl.activeTexture(gl.TEXTURE1);
  gl.u = createTexture(gl);
  gl.uniform1i(gl.getUniformLocation(program, "samplerU"), 1);
  gl.activeTexture(gl.TEXTURE2);
  gl.v = createTexture(gl);
  gl.uniform1i(gl.getUniformLocation(program, "samplerV"), 2);
};

/**
 * 编译着色器。
 * @param {WebGLRenderingContext} gl - WebGL 上下文。
 * @param {string} shaderSource - 着色器源代码。
 * @param {number} shaderType - 着色器类型，可以是 gl.VERTEX_SHADER 或 gl.FRAGMENT_SHADER。
 * @returns {WebGLShader | null} 编译成功返回着色器对象，失败返回 null。
 */

const compileShader = (
  gl: WebGLRenderingContext,
  shaderSource: string,
  shaderType: number
): WebGLShader | null => {
  const shader: WebGLShader | null = gl.createShader(shaderType);
  gl.shaderSource(shader, shaderSource);
  gl.compileShader(shader);
  const success: boolean = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!success) {
    const err = gl.getShaderInfoLog(shader);
    gl.deleteShader(shader);
    console.error("Could not compile shader", err);
    return null;
  }
  return shader;
};

/**
 * 创建并链接着色器程序。
 * @param {WebGLRenderingContext} gl - WebGL 上下文。
 * @param {WebGLShader} vertexShader - 顶点着色器对象。
 * @param {WebGLShader} fragmentShader - 片段着色器对象。
 * @returns {WebGLProgram | null} 链接成功返回着色器程序对象，失败返回 null。
 */

const createProgram = (
  gl: WebGLRenderingContext,
  vertexShader: WebGLShader,
  fragmentShader: WebGLShader
): WebGLProgram | null => {
  const program: WebGLProgram | null = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  gl.useProgram(program);
  const success: boolean = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!success) {
    console.error("Program failed to link", gl.getShaderInfoLog(program));
    return null;
  }
  return program;
};

/**
 * 创建 WebGL 纹理对象。
 * @param {WebGLRenderingContext} gl - WebGL 上下文。
 * @param {number} filter - 纹理过滤器，可以是 gl.LINEAR 等。
 * @returns {WebGLTexture | null} 创建成功返回纹理对象，失败返回 null。
 */

const createTexture = (
  gl: WebGLRenderingContext,
  filter: number = gl.LINEAR
): WebGLTexture | null => {
  const texture: WebGLTexture | null = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
  return texture;
};

/**
 * 在 WebGL 中绘制图像。
 * @param {HTMLCanvasElement} canvas - WebGL 上下文所在的 canvas 元素。
 * @param {number} width - 图像宽度。
 * @param {number} height - 图像高度。
 * @param {Uint8Array} data - 包含图像数据的 Uint8Array。
 */

export const renderImg = (canvas, width, height, data): void => {
  const gl =
    canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  const uOffset: number = width * height;
  const vOffset: number = (width >> 1) * (height >> 1);
  gl.bindTexture(gl.TEXTURE_2D, gl.y);
  gl.texImage2D(
    gl.TEXTURE_2D,
    0,
    gl.LUMINANCE,
    width,
    height,
    0,
    gl.LUMINANCE,
    gl.UNSIGNED_BYTE,
    data.subarray(0, uOffset)
  );
  gl.bindTexture(gl.TEXTURE_2D, gl.u);
  gl.texImage2D(
    gl.TEXTURE_2D,
    0,
    gl.LUMINANCE,
    width >> 1,
    height >> 1,
    0,
    gl.LUMINANCE,
    gl.UNSIGNED_BYTE,
    data.subarray(uOffset, uOffset + vOffset)
  );
  gl.bindTexture(gl.TEXTURE_2D, gl.v);
  gl.texImage2D(
    gl.TEXTURE_2D,
    0,
    gl.LUMINANCE,
    width >> 1,
    height >> 1,
    0,
    gl.LUMINANCE,
    gl.UNSIGNED_BYTE,
    data.subarray(uOffset + vOffset, data.length)
  );
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
};

/**
 * 初始化 canvas 的分辨率。
 * @param {HTMLCanvasElement} canvas - 要初始化分辨率的 canvas 元素。
 * @param {number} width - 初始化的宽度。
 * @param {number} height - 初始化的高度。
 */

export const initialCanvas = (
  canvas: HTMLCanvasElement,
  width: number,
  height: number
): void => {
  canvas.width = width;
  canvas.height = height;
};

/**
 * 初始化顶点缓冲区。
 * @param {WebGLRenderingContext} gl - WebGL 上下文。
 * @param {WebGLProgram} program - 着色器程序。
 */
const initVertexBuffers = (
  gl: WebGLRenderingContext,
  program: WebGLProgram
): void => {
  const vertexBuffer: WebGLBuffer | null = gl.createBuffer();
  const vertexRectangle: Float32Array = new Float32Array([
    1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0
  ]);
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertexRectangle, gl.STATIC_DRAW);

  const vertexPositionAttribute: number = gl.getAttribLocation(
    program,
    "a_vertexPosition"
  );
  gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(vertexPositionAttribute);

  const textureRectangle: Float32Array = new Float32Array([
    1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0
  ]);
  const textureBuffer: WebGLBuffer | null = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, textureRectangle, gl.STATIC_DRAW);

  const textureCoord: number = gl.getAttribLocation(
    program,
    "a_texturePosition"
  );
  gl.vertexAttribPointer(textureCoord, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(textureCoord);
};
